#include "basic.h"
#include "host_management.h"
#include "List/simclist_impl.h"
#include "query_management.h"

pthread_mutex_t peer_myMutex;
int peer_mutexState = 0;

extern char* BOOT_IP;

void peer_initializeMutex() {
	if (peer_mutexState == 0) {
		pthread_mutex_init(&peer_myMutex, NULL);
		peer_mutexState = 1;
	}
}

void joinleave_routine(void *arguments) {

	host_mgt_struct_t *arg = arguments;

	int n;
	struct sockaddr_in addr_rcv;
	int rcv = -1;
	size_t len = sizeof(addr_rcv);

	while (1) {

		n = recvfrom(arg->socket, &rcv, sizeof(int), 0,
				(struct sockaddr*) &addr_rcv, &len);

		if (n < 0) {
			perror("errore nelle recvfrom");
			exit(-1);
		}

		printf("richiesta ricevuta %d\n", rcv);

		if (rcv == 1) {

			add_peer(arg->peer_list, addr_rcv.sin_addr.s_addr);
			rcv = 2;
			n = sendto(arg->socket, &rcv, sizeof(int), 0,
					(struct sockaddr*) &addr_rcv, sizeof(addr_rcv));
			if (n > 0) {
				printf("JoinACK inviato %d\n", rcv);
			}

			if (n < 0) {
				perror("errore nella sendto");
				exit(-1);
			}
			pthread_mutex_t peer_myMutex;

			update_peer(list_size(arg->peer_list));

		}

		if (rcv == 0) {

			delete_peer(arg->peer_list, addr_rcv.sin_addr.s_addr);

			delete_peer_from_list(arg->file_list, addr_rcv.sin_addr.s_addr);

			n = sendto(arg->socket, &rcv, sizeof(int), 0,
					(struct sockaddr*) &addr_rcv, sizeof(addr_rcv));
			if (n > 0) {

				printf("LeaveACK inviato %d\n", rcv);
			}

			if (n < 0) {
				perror("errore nella sendto");
				exit(-1);
			}

			update_peer(list_size(arg->peer_list));

		}

	}

	exit(0);
}

void answer_ping(void *arguments) {
	host_mgt_struct_t *arg = arguments;
	fd_set set;
	struct timeval timeout = { 15, 0 };
	struct sockaddr_in addr_rcv;
	int rcv, n, s;
	size_t len = sizeof(addr_rcv);

	FD_ZERO(&set);
	FD_SET(arg->socket, &set);

	while (1) {

		// se e' superpeer, select senza timeout in quanto answer ping si sveglia solo nel caso in cui il bootstrap si riavvii dopo una caduta
		// se peer timeout di 15 secondi
		if (arg->is_superpeer == 0) {
			timeout.tv_sec = 25;
			s = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
		} else if (arg->is_superpeer == 1)
			s = select(FD_SETSIZE, &set, NULL, NULL, NULL);

		if (s == 0 || s < 0) {
			printf("s e': %d\n", s);
			if (s == -1)
				printf("%d\n", errno);
			if (arg->is_superpeer == 0) {
				*(arg->is_alive) = 0; // se non ricevo ping il superpeer e' morto
				printf("Il superpeer e' caduto\n");
			}
			close(arg->socket);
			return;
		}

		if (FD_ISSET(arg->socket,&set)) {

			n = recvfrom(arg->socket, &rcv, sizeof(int), 0,
					(struct sockaddr*) &addr_rcv, &len);
			if (n < 0) {
				perror("errore nelle recvfrom");
				exit(-1);
			}

			if (arg->is_superpeer == 1 || (arg->is_superpeer == 0
					&& addr_rcv.sin_addr.s_addr == arg->superpeer_ip)) {

				n = sendto(arg->socket, &rcv, sizeof(int), 0,
						(struct sockaddr*) &addr_rcv, sizeof(addr_rcv));

				if (n > 0) {

				}

				if (n < 0) {
					perror("errore nella sendto di answer_ping");
					exit(-1);
				}

			}
		}

	}

	return;

}

void ping_peer_routine(void* arguments) {

	int number_ping = 0;

	host_mgt_struct_t *arg = arguments;
	list_t *peer_list = arg->peer_list;
	list_t *file_list = arg->file_list;

	while (1) {

		sleep(2);
		peer_initializeMutex();

		if (list_size(peer_list) != 0) {

			list_t lista;
			list_init(&lista);
			list_attributes_serializer(&lista,
					element_serializer_superpeer_list);

			list_attributes_seeker(&lista, ip_file_seeker);
			list_attributes_comparator(&lista, list_comparator_uint32_t);
			list_attributes_copy(&lista, list_meter_uint32_t, 1);

			list_peer_copy(peer_list, &lista);

			int k = list_size(&lista);
			int array[k];
			int i, s, n, rcv;
			struct sockaddr_in servaddr;
			struct timeval timeout; //timeout per la select
			fd_set rset; //insieme di descrittori per la select

			//inizializzo l'array temporaneo con valori tutti a 0 ( 0----->non connesso; 1------>connesso)

			for (i = 0; i < k; i++) {

				array[i] = 0;
			}

			// apro la socket
			int sockfd;
			if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
				perror("errore in socket");
				exit(1);
			}
			memset((void *) &servaddr, 0, sizeof(servaddr));
			servaddr.sin_family = AF_INET;
			servaddr.sin_port = htons(PING_PORT);

			// ciclo di sendto
			int ping_number;

			if (list_size(&lista) > 0) {

				ritenta_ping: for (ping_number = 0; ping_number < 6; ping_number++) {
					int h = 0;
					list_iterator_start(&lista);
					while (list_iterator_hasnext(&lista)) {

						in_addr_t * corrente = (in_addr_t*) list_iterator_next(
								&lista);

						servaddr.sin_addr.s_addr = *corrente;
						char* p = inet_ntoa(servaddr.sin_addr);

						if (sendto(sockfd, &h, sizeof(int), 0,
								(struct sockaddr *) &servaddr, sizeof(servaddr))
								< 0) {
							perror("errore in sendto 6");
						}

						h++;

					}
					list_iterator_stop(&lista);
				}

				/*preparazione alla select*/

				FD_ZERO(&rset);
				FD_SET(sockfd, &rset);

				while (1) {
					timeout.tv_sec = 3;
					s = select(FD_SETSIZE, &rset, NULL, NULL, &timeout);

					if (s == 0) {
						if (number_ping < 3) {
							number_ping++;
							goto ritenta_ping;
						}
						break;
					}

					if (FD_ISSET(sockfd, &rset)) {
						if (recvfrom(sockfd, &rcv, sizeof(int), 0, NULL, NULL)
								< 0) {
							perror("errore in recvfrom");
							break;
						}
						array[rcv] = 1;
					}

				}
				number_ping = 0;

				int f;
				for (f = 0; f < k; f++) {

					if (array[f] == 0) {

						in_addr_t *temp = (in_addr_t*) list_get_at(&lista, f);

						struct in_addr a;
						a.s_addr = *temp;
						printf("Disconnesso Peer: %s\n", inet_ntoa(a));
						pthread_mutex_lock(&peer_myMutex);
						n = list_locate(peer_list, temp);
						list_delete_at(peer_list, n);
						delete_peer_from_list(file_list, *temp);
						pthread_mutex_unlock(&peer_myMutex);
						update_peer(list_size(arg->peer_list));

					}

				}

			}
		}
	}
}

void update_peer(int peer_number) {
	int sockfd;
	struct sockaddr_in servaddr;
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("errore in socket");
		exit(1);
	}
	memset((void *) &servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(BOOT_UPDATE_PORT);
	inet_aton(BOOT_IP, &servaddr.sin_addr);

	if (sendto(sockfd, &peer_number, sizeof(int), 0,
			(struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
		perror("errore in sendto 7"); // non chiudere sgravo
	}
	printf("Inviato aggiornamento peer al bootstrap\n");
}

void rcv_list_socket_routine(void* arguments) {
	char file_name[80];

	check_sublist_struct_t* arg = arguments;

	if (read(arg->peerSocket, file_name, 50) < 0) {
		printf("errore in read rcv_list_socket_routine chiudo thread\n");
		return;
	}

	char *file_name2 = file_name;

	printf("richiesta lista da un superpeer: %s\n", file_name2);
	list_t *requested_list;
	elem_file_t *elem_check = list_seek(arg->my_file_list, file_name2);
	if (elem_check != NULL) {

		printf("File trovato nella mia lista\n");
		list_t fake_peer_list; // lista dei peer associati al superpeer
		list_init(&fake_peer_list);
		list_attributes_comparator(&fake_peer_list, list_comparator_uint32_t);
		list_attributes_seeker(&fake_peer_list, ip_file_seeker);
		list_attributes_copy(&fake_peer_list, list_meter_uint32_t, 1);
		list_attributes_serializer(&fake_peer_list,
				element_serializer_peer_list);
		add_peer(&fake_peer_list, arg->superpeer_ip);
		host_mgt_struct_t arguments;
		requested_list = &fake_peer_list;
		arguments.socket = arg->peerSocket;
		arguments.peer_list = requested_list;
		send_peer_list(&arguments);
		return;

	}
	elem_check = list_seek(arg->peer_file_list, file_name2);
	if (elem_check != NULL) {
		printf("File trovato nella lista dei miei peer\n");
		requested_list = elem_check->lista_peer;
		print_peer_list(requested_list);
		host_mgt_struct_t arguments;
		arguments.socket = arg->peerSocket;
		arguments.peer_list = requested_list;
		send_peer_list(&arguments);
		return;
	}

	size_t size = -1;
	if ((write(arg->peerSocket, &size, sizeof(size_t))) < 0) { // non ho trovato il file
		printf("errore in write su rcv_list_socket_routine chiudo thread\n");
		return;
	}
	return;
}

void query_serve_peer_socket_routine(void* arguments) {

	check_sublist_struct_t * arg = arguments;

	printf("in query_serve_peer_socket\n");

	char file_name[41];

	if (read(arg->peerSocket, file_name, 30) < 0) {
		perror("errore in read");
	}

	char file_name2[strlen(file_name)]; // ok
	strncpy(file_name2, file_name, strlen(file_name) - 1);
	file_name2[strlen(file_name) - 1] = '\0';

	char* elem = list_seek(arg->my_file_list, file_name2);

	if (elem != NULL) {

		printf("file trovato nella lista locale\n");

		list_t fake_peer_list; // lista dei peer associati al superpeer
		list_init(&fake_peer_list);
		list_attributes_comparator(&fake_peer_list, list_comparator_uint32_t);
		list_attributes_seeker(&fake_peer_list, ip_file_seeker);
		list_attributes_copy(&fake_peer_list, list_meter_uint32_t, 1);
		list_attributes_serializer(&fake_peer_list,
				element_serializer_peer_list);
		add_peer(&fake_peer_list, arg->superpeer_ip);

		host_mgt_struct_t arguments;
		arguments.peer_list = &fake_peer_list;
		arguments.socket = arg->peerSocket;

		send_peer_list(&arguments);
		return;

	}

	elem_file_t *elem2 = list_seek(arg->file_list, file_name2);
	if (elem2 != NULL) {
		printf("file trovato nella lista dei miei peer\n");

		host_mgt_struct_t arguments;
		arguments.peer_list = elem2->lista_peer;
		arguments.socket = arg->peerSocket;

		send_peer_list(&arguments);
		return;
	}

	printf("parte il query flooding\n");

	check_sublist_struct_t arguments_q;
	arguments_q.fileName = file_name2;
	arguments_q.peerSocket = arg->peerSocket;
	arguments_q.superpeer_ip = arg->superpeer_ip;
	query_flooding(&arguments_q);

}

